package externalflame.systemdesign.secondkill.service.impl;


import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import externalflame.systemdesign.secondkill.dao.OrderDao;
import externalflame.systemdesign.secondkill.dto.OrderDto;
import externalflame.systemdesign.secondkill.entity.Order;
import externalflame.systemdesign.secondkill.entity.SecKill;
import externalflame.systemdesign.secondkill.exception.*;
import externalflame.systemdesign.secondkill.service.OrderService;
import externalflame.systemdesign.secondkill.service.SecKillService;
import externalflame.systemdesign.secondkill.utils.SpringBeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;

@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    RedisTemplate<String, Number> redisTemplate;

    // TODO: 应该拆成独立的微服务。这里为了简单，放在了一块儿。
    @Resource
    SecKillService seckillService;

    @Resource
    OrderDao orderDao;

    IdentifierGenerator identifierGenerator = new DefaultIdentifierGenerator();

    @Resource
    TransactionManager transactionManager;


    @Override
    public Order add(Long userId, OrderDto orderDto) throws NoStockException, NoSuchSecKillException, PerPurchaseLimitException, NotValidPeriodException {
        SecKill seckill = seckillService.get(orderDto.getSecKillId());
        if (seckill == null) {
            throw new NoSuchSecKillException();
        }

        filterRequestUseRedis(userId, orderDto, seckill);
        // Redis 拦截了大量请求，少量请求可以走到这里，访问关系数据库。

        OrderServiceImpl service = SpringBeanUtils.getBean(OrderServiceImpl.class);
        Order order = service.addOrderUseRelationDB(userId, orderDto, seckill);
        return order;
    }


    private SecKill getSecKill(Long secKillId) {
        SecKill seckill = seckillService.get(secKillId);
        return seckill;
    }

    @Transactional
    public Order addOrderUseRelationDB(Long userID, OrderDto orderDto, SecKill seckill) {
        Order order = new Order();
        order.setId(identifierGenerator.nextId(Order.class).longValue());
        order.setProductId(orderDto.getProductId());
        order.setNum(orderDto.getNum());
        order.setUserId(userID);
        orderDao.insert(order);
        // 使用原子性处理并发
        int changed = orderDao.updateStock(orderDto.getProductId(), orderDto.getNum(), orderDto.getSecKillId());
        if (changed <= 0) {
            // 回滚
            throw new NoStockException();
        }

        // 检查限购条件
        // 虽然 insert or update 的操作，没法保证 (userId, productId, secKillId) 记录唯一，但是我们通过唯一索引保证了这一点。
        if (orderDao.hasPerchasedRecord(userID, orderDto.getProductId(), orderDto.getSecKillId()) <= 0) {
            orderDao.insertPurchasedRecord(userID, orderDto.getProductId(), orderDto.getSecKillId());
        }
        changed = orderDao.updateUserPurchased(userID, orderDto.getProductId(), orderDto.getNum(), seckill.getNumLimit(), orderDto.getSecKillId());
        if (changed <= 0) {
            // 回滚
            throw new PerPurchaseLimitException();
        }
        return order;
    }

    private void filterRequestUseRedis(Long userId, OrderDto orderDto, SecKill seckill) throws NoSuchSecKillException, NotValidPeriodException, NoStockException, PerPurchaseLimitException {
        Date now = new Date();
        if (seckill.getStartTime() != null && seckill.getEndTime() != null && (seckill.getStartTime().after(now) || seckill.getEndTime().before(now))) {
            throw new NotValidPeriodException();
        }

        String stockKey = String.format("productId:%s", orderDto.getProductId());
        Number stock = redisTemplate.opsForValue().get(stockKey);
        if (stock != null && stock.longValue() <= 0) {
            throw new NoStockException();
        }
        String purchasedKey = String.format("productId:%s,userId:%s", orderDto.getProductId(), userId);
        Number purchased = redisTemplate.opsForValue().get(purchasedKey);

        if (purchased == null) {
            // redis 中没有，查询订单表，确认用户购买了多少个。
            purchased = orderDao.findCntByUserId(userId, orderDto.getProductId(), orderDto.getSecKillId());
            redisTemplate.opsForValue().set(purchasedKey, purchased);
        }

        if (purchased != null && purchased.longValue() + orderDto.getNum() > seckill.getNumLimit()) {
            throw new PerPurchaseLimitException();
        }
    }
}
